package com.dmj.sqldsl.wrapper;

import static com.dmj.sqldsl.builder.column.ColumnBuilders.all;
import static com.dmj.sqldsl.builder.column.ColumnBuilders.count;
import static com.dmj.sqldsl.model.condition.ConditionMethod.eq;
import static com.dmj.sqldsl.model.condition.ConditionMethod.in;
import static com.dmj.sqldsl.utils.CollectionUtils.asModifiableList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;

import com.dmj.sqldsl.builder.DeleteBuilder;
import com.dmj.sqldsl.builder.DslQueryBuilder;
import com.dmj.sqldsl.builder.SelectBuilder;
import com.dmj.sqldsl.builder.UpdateWhereBuilder;
import com.dmj.sqldsl.builder.config.GlobalConfig;
import com.dmj.sqldsl.builder.exception.NoIdColumnException;
import com.dmj.sqldsl.builder.table.EntityLayout;
import com.dmj.sqldsl.builder.table.EntityLayout.EntityField;
import com.dmj.sqldsl.executor.BatchResult;
import com.dmj.sqldsl.executor.SqlDialect;
import com.dmj.sqldsl.executor.SqlDslExecutor;
import com.dmj.sqldsl.executor.exception.NoOrderByException;
import com.dmj.sqldsl.model.DslDelete;
import com.dmj.sqldsl.model.DslQuery;
import com.dmj.sqldsl.model.Entity;
import com.dmj.sqldsl.model.Order;
import com.dmj.sqldsl.model.OrderBy;
import com.dmj.sqldsl.model.Where;
import com.dmj.sqldsl.model.column.ListValueColumn;
import com.dmj.sqldsl.model.column.SimpleColumn;
import com.dmj.sqldsl.model.column.ValueColumn;
import com.dmj.sqldsl.model.condition.Condition;
import com.dmj.sqldsl.model.condition.ConditionMethod;
import com.dmj.sqldsl.model.condition.Conditions;
import com.dmj.sqldsl.model.table.SimpleTable;
import com.dmj.sqldsl.wrapper.page.Page;
import com.dmj.sqldsl.wrapper.page.PageRequest;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class WrapperExecutor {

  private SqlDslExecutor executor;

  public <T> List<T> select(Class<T> entityClass) {
    return select(new QueryWrapper<>(entityClass));
  }

  public <T> List<T> select(Wrapper<T> wrapper) {
    DslQueryBuilder queryBuilder = wrapper.toQueryBuilder();
    return executor.execute(queryBuilder.toQuery(), wrapper.getEntityClass());
  }

  public <T> Page<T> select(PageRequest request, Class<T> entityClass) {
    return select(request, new QueryWrapper<>(entityClass));
  }

  public <T> Page<T> select(PageRequest request, QueryWrapper<T> wrapper) {
    return select(wrapper.toQueryBuilder(), request, wrapper.getEntityClass());
  }

  private <T> Page<T> select(DslQueryBuilder queryBuilder, PageRequest request,
      Class<T> entityClass) {
    OrderBy orderBy = null;
    if (SqlDialect.sqlserver.equals(executor.getDialect())) {
      EntityLayout entityLayout = GlobalConfig.getEntityConfig().getEntityLayout(entityClass);
      EntityField idField = entityLayout.getId().orElseThrow(
          () -> new NoOrderByException(
              String.format(
                  "Sql server need order statement when call limit! "
                      + "but the table %s don't have id, can't auto generate order statement!",
                  entityLayout.getTableName())));
      orderBy = new OrderBy(singletonList(new Order(
          new SimpleColumn(entityLayout.getEntityType().getId(), idField.getFieldName(),
              idField.getColumnName()), true)));
    }
    SelectBuilder selectBuilder = queryBuilder.getSelectBuilder();

    queryBuilder.setSelectBuilder(new SelectBuilder().select(count(all())));
    long total = executor.execute(queryBuilder.toQuery(), Long.class).get(0);
    if (total == 0) {
      return new Page<>(request, 0, emptyList());
    }

    queryBuilder.setSelectBuilder(selectBuilder);
    DslQuery dslQuery = queryBuilder
        .limit(request.getNumber() - 1, request.getSize())
        .toQuery();
    dslQuery.setOrderBy(orderBy);
    List<T> resultList = executor.execute(dslQuery, entityClass);

    return new Page<>(request, total, resultList);
  }

  public <T> int save(T entity) {
    return executor.save(GlobalConfig.getEntityConfig().getTableEntity(entity));
  }

  public <T> BatchResult saveBatch(List<T> entities) {
    List<Entity> entityList = entities.stream()
        .map(e -> GlobalConfig.getEntityConfig().getTableEntity(e))
        .collect(toList());
    return executor.saveBatch(entityList);
  }

  @SuppressWarnings("unchecked")
  public <T> int delete(Class<?> entityClass, T id) {
    ValueColumn valueColumn;
    ConditionMethod method;
    if (id instanceof Collection) {
      valueColumn = new ListValueColumn((Collection<?>) id);
      method = in;
    } else if (id.getClass().isArray()) {
      valueColumn = new ListValueColumn(Arrays.asList((T[]) id));
      method = in;
    } else {
      valueColumn = new ValueColumn(id);
      method = eq;
    }
    EntityLayout entityLayout = GlobalConfig.getEntityConfig().getEntityLayout(entityClass);
    String tableId = entityLayout.getEntityType().getId();
    EntityField idField = entityLayout.getId().orElseThrow(NoIdColumnException::new);
    DslDelete delete = DslDelete.builder()
        .table(new SimpleTable(entityLayout.getTableName(), tableId))
        .where(new Where(new Conditions(asModifiableList(new Condition(
            new SimpleColumn(tableId, idField.getFieldName(), idField.getColumnName()), method,
            valueColumn)))))
        .build();
    return executor.delete(delete);
  }

  public <T> int delete(Wrapper<T> wrapper) {
    DeleteBuilder deleteBuilder = wrapper.toDeleteBuilder();
    return executor.delete(deleteBuilder.toDelete());
  }

  public <T> int update(Wrapper<T> updateWrapper) {
    UpdateWhereBuilder<T> updateBuilder = updateWrapper.toUpdateBuilder();
    return executor.update(updateBuilder.toUpdate());
  }
}
